package ws.jdbc.concurrent.driver.sqlserver.tds;

import java.sql.Date;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.TYPE_INFO;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.TYPE_VARBYTE;
import ws.jdbc.concurrent.driver.sqlserver.tds.rpc.UpdateValue;

/**
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class TokenCollection implements ITDSResultSet {

  private ArrayList<TokenReader> readerList = new ArrayList<TokenReader>();
  /**
   * List with all COLINFO tokens.
   */
  private LinkedList<ColInfoTokenReader> colInfoList = new LinkedList<ColInfoTokenReader>();
  /**
   * List with all DONE tokens.
   */
  private LinkedList<DoneTokenReader> doneList = new LinkedList<DoneTokenReader>();
  /**
   * List with all DONEINPROC tokens.
   */
  private LinkedList<DoneInProcTokenReader> doneInProcList = new LinkedList<DoneInProcTokenReader>();
  /**
   * List with all DONEPROC tokens.
   */
  private LinkedList<DoneProcTokenReader> doneProcList = new LinkedList<DoneProcTokenReader>();
  /**
   * List with all ENVCHANGE tokens.
   */
  private LinkedList<EnvChangeTokenReader> envChangeList = new LinkedList<EnvChangeTokenReader>();
  /**
   * List with all ERROR tokens.
   */
  private LinkedList<ErrorTokenReader> errorList = new LinkedList<ErrorTokenReader>();
  /**
   * List with all INFO tokens.
   */
  private LinkedList<InfoTokenReader> infoList = new LinkedList<InfoTokenReader>();
  /**
   * List with all LOGINACK tokens.
   *
   * <p>
   * Since in each message there will be at most
   * one LOGINACK token this is just an instance instead of a list.
   * </p>
   */
  private LoginAckTokenReader loginAck = null;
  /**
   * List with all ROW tokens.
   */
  private LinkedList<RowTokenReader> rowList = new LinkedList<RowTokenReader>();
  /**
   * List with all RETURNVALUE tokens.
   */
  private LinkedList<ReturnValueTokenReader> returnValueList = new LinkedList<ReturnValueTokenReader>();
  /**
   * List with all RETURNSTATUS tokens.
   */
  private LinkedList<ReturnStatusTokenReader> returnStatusList = new LinkedList<ReturnStatusTokenReader>();
  /**
   * List with all TABNAME tokens.
   */
  private LinkedList<TabNameTokenReader> tabNameList = new LinkedList<TabNameTokenReader>();
  private ColMetadataTokenReader colMeta = null;
  private int cursor;

  /**
   * Create a new token collection instance.
   */
  public TokenCollection() {
    this.rowList = new LinkedList<RowTokenReader>();
  }

  public TokenCollection(int cursorId, ColMetadataTokenReader colMeta) {
    this();
    this.cursor = cursorId;
    this.colMeta = colMeta;
  }

  void addReader(TokenReader reader) {
    readerList.add(reader);
  }

  int size() {
    return readerList.size();
  }

  void addColInfo(ColInfoTokenReader colInfoTokenReader) {
    colInfoList.add(colInfoTokenReader);
  }

  void addColMetadata(ColMetadataTokenReader colMeta) {
    this.colMeta = colMeta;
  }

  void addDone(DoneTokenReader doneTokenReader) {
    doneList.add(doneTokenReader);
  }

  void addDoneInProc(DoneInProcTokenReader doneInProcTokenReader) {
    doneInProcList.add(doneInProcTokenReader);
  }

  void addDoneProc(DoneProcTokenReader doneProcTokenReader) {
    doneProcList.add(doneProcTokenReader);
  }

  void addEnvChange(EnvChangeTokenReader envChangeTokenReader) {
    envChangeList.add(envChangeTokenReader);
  }

  void addError(ErrorTokenReader errorTokenReader) {
    errorList.add(errorTokenReader);
  }

  void addInfo(InfoTokenReader infoTokenReader) {
    infoList.add(infoTokenReader);
  }

  void addLoginAck(LoginAckTokenReader loginAck) {
    this.loginAck = loginAck;
  }

  void addReturnStatus(ReturnStatusTokenReader returnStatusTokenReader) {
    returnStatusList.add(returnStatusTokenReader);
  }

  void addRow(RowTokenReader row) {
    rowList.add(row);
  }

  void addTabName(TabNameTokenReader tabNameTokenReader) {
    tabNameList.add(tabNameTokenReader);
  }

  public List<ColumnData> getColumnsMetadata() {
    return colMeta.getColumnsMetaData();
  }

  void addReturnValue(ReturnValueTokenReader value) {
    returnValueList.add(value);
  }

  boolean hasLoginAck() {
    return loginAck != null ? true : false;
  }

  void throwExceptionIfErrorExists() throws SQLException {
    if (!errorList.isEmpty()) {
      ErrorTokenReader error = errorList.getFirst();
      throw new SQLException(error.getReason(), error.getSQLState());
    }
  }

  @Override
  public String toString() {
    return "TokenCollection=\n"
            + readerList;
  }

  /**
   * Get the return value for a requested parameter.
   * 
   * @param paramOrdinal ordinal position of the output parameter.
   * 
   * @return return value as TYPE_VARBYTE object, or null if the ordinal position
   * doesn't exist.
   */
  TYPE_VARBYTE getReturnValue(int paramOrdinal) {
    for (ReturnValueTokenReader value : returnValueList) {
      if (value.getParamOrdinal() == paramOrdinal) {
        return value.getValue();
      }
    }
    return null;
  }

  public void setCursor() {
    this.cursor = getReturnValue(0).asInt();
  }

  public void setCursor(final int cursor) {
    this.cursor = cursor;
  }

  public int getCursor() {
    return cursor;
  }

  public int columnCount() {
    // -1 because of the ROWSTAT column
    return colMeta.getColumnsMetaData().size() - 1;
  }

  public int rowCount() {
    return rowList.size();
  }

  public boolean isEmpty() {
    return rowList.size() == 0;
  }

  public int getInt(int rowNumber, int columnNumber) {
    return rowList.get(rowNumber).
            getData(columnNumber).
            asInt();
  }

  public String getString(int rowNumber, int columnNumber) {
    return rowList.get(rowNumber).
            getData(columnNumber).
            asString();
  }

  public float getFloat(int rowNumber, int columnNumber) {
    return rowList.get(rowNumber).
            getData(columnNumber).
            asFloat();
  }

  public double getDouble(int rowNumber, int columnNumber) {
    return rowList.get(rowNumber).
            getData(columnNumber).
            asDouble();
  }

  public Date getDate(int rowNumber, int columnNumber) {
    return rowList.get(rowNumber).
            getData(columnNumber).
            asDate();
  }

  public boolean wasNull(int rowNumber, int columnNumber) {
    return rowList.get(rowNumber).
            getData(columnNumber).isNull();
  }

  public String getColumnName(int columnIndex) {
    return colMeta.getColumnName(columnIndex);
  }

  public TYPE_INFO getColumnInfo(int columnIndex) {
    return colMeta.getColumnInfo(columnIndex);
  }

  public String getTableName() {
    if (tabNameList.isEmpty()) {
      return "";
    } else {
      return tabNameList.getFirst().getTableName();
    }
  }

  public int getUpdateRowCount() {
    if (doneList.isEmpty()) {
      return 0;
    } else {
      return (int) doneList.getFirst().getDoneRowCount();
    }
  }

  public void addRows(ITDSResultSet tdsResultSet) {
    this.rowList.addAll(((TokenCollection) tdsResultSet).rowList);
  }

  public void updateRow(int rowIndex, RowTokenReader row) {
    this.rowList.set(rowIndex, row);
  }

  public void updateRow(int rowIndex, UpdateValue[] updateValues) {
    RowTokenReader row = rowList.get(rowIndex);
    for (int i = 0; i < updateValues.length; i++) {
      if (updateValues[i].getValue() != null) {
        row.setData(i, updateValues[i].getValue());
      }
    }
  }

  public void insertValues(UpdateValue[] updateValues) {
    RowTokenReader row = new RowTokenReader(updateValues.length);
    for (int i = 0; i < updateValues.length; i++) {
      row.addData(i, updateValues[i].getValue());
    }
    rowList.add(row);
  }

  public RowTokenReader getRow(int rowIndex) {
    return rowList.get(rowIndex);
  }

  public ColMetadataTokenReader getColumnMetadata() {
    return colMeta;
  }

  public void replaceRows(ITDSResultSet tdsResultSet) {
    rowList.clear();
    addRows(tdsResultSet);
  }
}
